/*
*   Copyright 2011 Matthew Crinklaw-Vogt
*
*   Licensed under the Apache License, Version 2.0 (the "License");
*   you may not use this file except in compliance with the License.
*   You may obtain a copy of the License at
*
*       http://www.apache.org/licenses/LICENSE-2.0
*
*   Unless required by applicable law or agreed to in writing, software
*   distributed under the License is distributed on an "AS IS" BASIS,
*   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*   See the License for the specific language governing permissions and
*  limitations under the License.
*/

package com.tantaman.util.ref;

import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.Set;
import java.util.concurrent.ExecutorService;

import com.tantaman.util.concurrent.ConcurrentHashSet;
import com.tantaman.util.ref.builder.ObservableRefBuilder;

public class GCNotifier {
	private final Set<ObservableReference<?>> mRegisteredReferences;
	private final ExecutorService mUnqueueExecutor;
	private final ExecutorService mNotifyExecutor;
	private final ReferenceQueue<Object> mRefQueue;
	private volatile boolean mShutdown;
	
	private final Runnable mUnqueueTask = new Runnable() {
		@Override
		public void run() {
			while (!Thread.interrupted() && !mShutdown) {
				try {
					Reference<?> ref = mRefQueue.remove();
					mRegisteredReferences.remove(ref);
					ObservableReference<?> observableRef = (ObservableReference<?>)ref;
					
					mNotifyExecutor.submit(new NotifyRunnable(observableRef));
				} catch (InterruptedException e) {
				}
			}
		}
	};
	
	private class NotifyRunnable implements Runnable {
		ObservableReference<?> mRef;
		public NotifyRunnable(ObservableReference<?> pRef) {
			mRef = pRef;
		}
		
		@Override
		public void run() {
			((ObservableReferenceProtectedMethods)mRef).notifyObservers();
		}
	}
	
	public GCNotifier(ExecutorService pUnqeueExecutor, ExecutorService pNotifyExecutor) {
		mRegisteredReferences = new ConcurrentHashSet<ObservableReference<?>>();
		mUnqueueExecutor = pUnqeueExecutor;
		mNotifyExecutor = pNotifyExecutor;
		mRefQueue = new ReferenceQueue<Object>();
		
		mUnqueueExecutor.submit(mUnqueueTask);
	}
	
	public <IDENTIFIER_T> ObservableReference<IDENTIFIER_T> registerObject(Object pObj, ReferenceType pRefType, IDENTIFIER_T pIdentifier) {
		ObservableRefBuilder<Object> builder = 
			(ObservableRefBuilder<Object>) pRefType.createBuilder();
		
		ObservableReference<IDENTIFIER_T> ref = (ObservableReference<IDENTIFIER_T>)
			builder
				.registerIdentifier(pIdentifier)
				.setReferent(pObj)
				.setReferenceQueue(mRefQueue).build();
		
		mRegisteredReferences.add(ref);
		
		return ref;
	}
	
	public <IDENTIFIER_T> ObservableReference<IDENTIFIER_T> registerObject(Object pObj, ReferenceType pRefType) {
		return registerObject(pObj, pRefType, null);
	}
	
	public ObservableReference<?> registerObject(Object pObj) {
		return registerObject(pObj, ReferenceType.OBSERVABLE_WEAK_REF, null);
	}
}
